/**
 * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
 * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
 */
/**
 * @module table/tablemouse
 */
import { Plugin } from 'ckeditor5/src/core';
import TableSelection from './tableselection';
import MouseEventsObserver from './tablemouse/mouseeventsobserver';
import TableUtils from './tableutils';
/**
 * This plugin enables a table cells' selection with the mouse.
 * It is loaded automatically by the {@link module:table/table~Table} plugin.
 */
export default class TableMouse extends Plugin {
    /**
     * @inheritDoc
     */
    static get pluginName() {
        return 'TableMouse';
    }
    /**
     * @inheritDoc
     */
    static get requires() {
        return [TableSelection, TableUtils];
    }
    /**
     * @inheritDoc
     */
    init() {
        const editor = this.editor;
        // Currently the MouseObserver only handles `mousedown` and `mouseup` events.
        // TODO move to the engine?
        editor.editing.view.addObserver(MouseEventsObserver);
        this._enableShiftClickSelection();
        this._enableMouseDragSelection();
    }
    /**
     * Enables making cells selection by <kbd>Shift</kbd>+click. Creates a selection from the cell which previously held
     * the selection to the cell which was clicked. It can be the same cell, in which case it selects a single cell.
     */
    _enableShiftClickSelection() {
        const editor = this.editor;
        const tableUtils = editor.plugins.get(TableUtils);
        let blockSelectionChange = false;
        const tableSelection = editor.plugins.get(TableSelection);
        this.listenTo(editor.editing.view.document, 'mousedown', (evt, domEventData) => {
            const selection = editor.model.document.selection;
            if (!this.isEnabled || !tableSelection.isEnabled) {
                return;
            }
            if (!domEventData.domEvent.shiftKey) {
                return;
            }
            const anchorCell = tableSelection.getAnchorCell() || tableUtils.getTableCellsContainingSelection(selection)[0];
            if (!anchorCell) {
                return;
            }
            const targetCell = this._getModelTableCellFromDomEvent(domEventData);
            if (targetCell && haveSameTableParent(anchorCell, targetCell)) {
                blockSelectionChange = true;
                tableSelection.setCellSelection(anchorCell, targetCell);
                domEventData.preventDefault();
            }
        });
        this.listenTo(editor.editing.view.document, 'mouseup', () => {
            blockSelectionChange = false;
        });
        // We need to ignore a `selectionChange` event that is fired after we render our new table cells selection.
        // When downcasting table cells selection to the view, we put the view selection in the last selected cell
        // in a place that may not be natively a "correct" location. This is – we put it directly in the `<td>` element.
        // All browsers fire the native `selectionchange` event.
        // However, all browsers except Safari return the selection in the exact place where we put it
        // (even though it's visually normalized). Safari returns `<td><p>^foo` that makes our selection observer
        // fire our `selectionChange` event (because the view selection that we set in the first step differs from the DOM selection).
        // Since `selectionChange` is fired, we automatically update the model selection that moves it that paragraph.
        // This breaks our dear cells selection.
        //
        // Theoretically this issue concerns only Safari that is the only browser that do normalize the selection.
        // However, to avoid code branching and to have a good coverage for this event blocker, I enabled it for all browsers.
        //
        // Note: I'm keeping the `blockSelectionChange` state separately for shift+click and mouse drag (exact same logic)
        // so I don't have to try to analyze whether they don't overlap in some weird cases. Probably they don't.
        // But I have other things to do, like writing this comment.
        this.listenTo(editor.editing.view.document, 'selectionChange', evt => {
            if (blockSelectionChange) {
                // @if CK_DEBUG // console.log( 'Blocked selectionChange to avoid breaking table cells selection.' );
                evt.stop();
            }
        }, { priority: 'highest' });
    }
    /**
     * Enables making cells selection by dragging.
     *
     * The selection is made only on mousemove. Mouse tracking is started on mousedown.
     * However, the cells selection is enabled only after the mouse cursor left the anchor cell.
     * Thanks to that normal text selection within one cell works just fine. However, you can still select
     * just one cell by leaving the anchor cell and moving back to it.
     */
    _enableMouseDragSelection() {
        const editor = this.editor;
        let anchorCell, targetCell;
        let beganCellSelection = false;
        let blockSelectionChange = false;
        const tableSelection = editor.plugins.get(TableSelection);
        this.listenTo(editor.editing.view.document, 'mousedown', (evt, domEventData) => {
            if (!this.isEnabled || !tableSelection.isEnabled) {
                return;
            }
            // Make sure to not conflict with the shift+click listener and any other possible handler.
            if (domEventData.domEvent.shiftKey || domEventData.domEvent.ctrlKey || domEventData.domEvent.altKey) {
                return;
            }
            anchorCell = this._getModelTableCellFromDomEvent(domEventData);
        });
        this.listenTo(editor.editing.view.document, 'mousemove', (evt, domEventData) => {
            if (!domEventData.domEvent.buttons) {
                return;
            }
            if (!anchorCell) {
                return;
            }
            const newTargetCell = this._getModelTableCellFromDomEvent(domEventData);
            if (newTargetCell && haveSameTableParent(anchorCell, newTargetCell)) {
                targetCell = newTargetCell;
                // Switch to the cell selection mode after the mouse cursor left the anchor cell.
                // Switch off only on mouseup (makes selecting a single cell possible).
                if (!beganCellSelection && targetCell != anchorCell) {
                    beganCellSelection = true;
                }
            }
            // Yep, not making a cell selection yet. See method docs.
            if (!beganCellSelection) {
                return;
            }
            blockSelectionChange = true;
            tableSelection.setCellSelection(anchorCell, targetCell);
            domEventData.preventDefault();
        });
        this.listenTo(editor.editing.view.document, 'mouseup', () => {
            beganCellSelection = false;
            blockSelectionChange = false;
            anchorCell = null;
            targetCell = null;
        });
        // See the explanation in `_enableShiftClickSelection()`.
        this.listenTo(editor.editing.view.document, 'selectionChange', evt => {
            if (blockSelectionChange) {
                // @if CK_DEBUG // console.log( 'Blocked selectionChange to avoid breaking table cells selection.' );
                evt.stop();
            }
        }, { priority: 'highest' });
    }
    /**
     * Returns the model table cell element based on the target element of the passed DOM event.
     *
     * @returns Returns the table cell or `undefined`.
     */
    _getModelTableCellFromDomEvent(domEventData) {
        // Note: Work with positions (not element mapping) because the target element can be an attribute or other non-mapped element.
        const viewTargetElement = domEventData.target;
        const viewPosition = this.editor.editing.view.createPositionAt(viewTargetElement, 0);
        const modelPosition = this.editor.editing.mapper.toModelPosition(viewPosition);
        const modelElement = modelPosition.parent;
        return modelElement.findAncestor('tableCell', { includeSelf: true });
    }
}
function haveSameTableParent(cellA, cellB) {
    return cellA.parent.parent == cellB.parent.parent;
}
